module moonglide.renderer.opengl.OpenGLFilter;

import
//    moonglide.renderer.opengl.OpenGLRenderer,
    moonglide.renderer.IRendererBind,
    moonglide.renderer.IRenderer,
    moonglide.renderer.opengl.OpenGLOffscreen,
    moonglide.renderer.opengl.GL,
    moonglide.renderer.opengl.OpenGLRenderer;
/*	moonglide.renderer.IRendererOffscreen,

	moonglide.renderer.opengl.GL,
	moonglide.renderer.opengl.OpenGLRenderer,

	moonglide.util.MoonUtil,
	moonglide.io.LogSystem,
	moonglide.math.MoonMath,

	moonglide.resources.resproc.texture.ResPTex;
	*/
class CFilter
{
    protected:
        IRendererBind _binder;
        IRenderer _render;

        char[] _id;
        CResTexture[char[]] _input;
        CResTexture _output = null;

    public:

        void process()
        in
        {
            moonAssert( isInit, "please, initialize the filter", __FILE__, __LINE__ );
        }
        body
        {
        }

        bool isInit()
        {
            return true;
        }

        bool setInput(char[] name, CResTexture tex)
        in
        {

            moonAssert( (name in _input) !is null , "attribute not found", __FILE__, __LINE__);
        }
        body
        {
            _input[name] = tex;
            return true;
        }

        bool setOutput(CResTexture tex)
        {
            _output = tex;
            return true;
        }

        char[] id() { return _id; }

        this(char[] idname, IRenderer bin)
        {
            _id = idname;

            _binder = bin.materialSys.resBinder;
            _render = bin;
        }

        ~this()
        {
        	_input = null;
        	_output = null;
        }
}

class CMoveFilter : CFilter
{
    protected:
        COpenGLFBO _buf;

    public:
        static const char[] inTexName = "intex";

        void process()
        {
            super.process();
            _buf.bindAndAttachTexture2d(_output);

            _binder.bindTexture(_input[inTexName]);
            _render.fullScreenQuad();
        }

        bool isInit()
        {
            return (_input[inTexName] !is null) && (_output !is null) && (_buf.isInited());;
        }

        bool setOutput(CResTexture tex)
        {
            _buf.resize( tex.data[0].width, tex.data[0].height );
            return super.setOutput( tex );
        }

        this(char[] idname, IRenderer bin)
        {
            super(idname, bin);

            _input[inTexName] = null;

            _buf = new COpenGLFBO(64,64, false);
            _buf.init();
        }

        ~this()
        {
            delete _buf;
        }
}

class CBlendOneOneFilter : CFilter
{
    protected:
        COpenGLFBO _bufAdd;

    public:
        static const char[] inTexName1 = "intex1";
        static const char[] inTexName2 = "intex2";

        void process()
        {
            super.process();

            _bufAdd.bindAndAttachTexture2d(_output);

            _binder.bindTexture(_input[inTexName1]);
//            glDisable(GL_BLEND);
            _render.fullScreenQuad();

            _binder.bindTexture(_input[inTexName2]);
            glEnable(GL_BLEND);
            glBlendFunc(GL_ONE,GL_ONE);
            _render.fullScreenQuad();
            glDisable(GL_BLEND);

            _bufAdd.unBind;

        }
        bool isInit()
        {
            return (_input[inTexName2] !is null) && (_input[inTexName1] !is null) && (_output !is null) && (_bufAdd.isInited());
        }

        bool setOutput(CResTexture tex)
        {

            _bufAdd.resize( tex.data[0].width, tex.data[0].height );
            return super.setOutput( tex );
        }

        this(char[] idname, IRenderer bin)
        {
            super(idname, bin);
            _input[inTexName1] = null;
            _input[inTexName2] = null;
            _bufAdd = new COpenGLFBO(64,64,false);
            _bufAdd.init();
        }

        ~this()
        {
            delete _bufAdd;
        }

}

private class CLinearFilter : CFilter
{
    protected:
        COpenGLFBO _bufLin;
        CResMaterial _lin;

    public:
        static const char[] inTexName = "intex";

        void process()
        {
            super.process();

            setOutput(_output);
            _bufLin.bindAndAttachTexture2d(_output);

            _lin.textures[0] = _input[inTexName];
            _binder.bindMaterial (_lin);

            glDisable(GL_BLEND);
            _render.fullScreenQuad();

            _binder.unBindMaterial (_lin);
        }

        bool isInit()
        {
            return (_input[inTexName] !is null) && (_output !is null) && (_bufLin.isInited());
        }

        bool setOutput(CResTexture tex)
        {
            _bufLin.resize( tex.data[0].width, tex.data[0].height );
            return super.setOutput( tex );
        }

        this(char[] idname, IRenderer bin)
        {
//            moonAssert( this.classinfo !is CLinearFilter.classinfo, idname~" is CLinearFilter, that is not for use", __FILE__, __LINE__ );
            super(idname, bin);
            _input[inTexName] = null;
            _lin = CResMaterial(idname~"_matpass");
            _lin.bools["specialShaderUse"] = true;
            _lin.textures.length = 1;

            _bufLin = new COpenGLFBO(64,64,false);
            _bufLin.init();
        }

        ~this()
        {
            delete _bufLin;
        }
}

class CBrightFilter : CLinearFilter
{
    public:
        this(char[] idname, IRenderer bin)
        {
            super(idname, bin);
            _lin.shader.setProg( sh_Vertex , CResShader( "data/v_bright.glsl", true ));
			_lin.shader.setProg( sh_Fragment, CResShader( "data/f_bright.glsl", true ));
			_lin.shader.addLocal( "brightimage", 0 , true );
        }
}

class CSepiaFilter : CLinearFilter
{
    public:
        this(char[] idname, IRenderer bin)
        {
            super(idname, bin);
            _lin.shader.setProg( sh_Vertex , CResShader( "data/v_sepia.glsl", true ));
			_lin.shader.setProg( sh_Fragment, CResShader( "data/f_sepia.glsl", true ));
			_lin.shader.addLocal ("image", 0 , true);
        }
}

class CMonoFilter : CLinearFilter
{
    public:
        this(char[] idname, IRenderer bin)
        {
            super(idname, bin);
            _lin.shader.setProg( sh_Vertex , CResShader( "data/f_mono.glsl", true ));
			_lin.shader.setProg( sh_Fragment, CResShader( "data/f_mono.glsl", true ));
			_lin.shader.addLocal( "image", 0 , true);
        }
}

class CZtoRGBFilter : CLinearFilter
{
    public:
        this(char[] idname, IRenderer bin)
        {
            super(idname, bin);
            _lin.shader.setProg( sh_Vertex , CResShader( "data/v_renderz.glsl", true ));
			_lin.shader.setProg( sh_Fragment, CResShader( "data/f_renderz.glsl", true ));
			_lin.shader.addLocal( "depthsampler", 0 , true );

        }
}


class CDistortFilter : CLinearFilter
{
    protected:
        float _time = 0.f;
    public:
        static const char[] inTexName1 = "intex1";
        static const char[] inTexName2 = "intex2";

        bool isInit()
        {
            return ((_input[inTexName2] !is null) && (_input[inTexName1] !is null) && (_input[inTexName] !is null) && (_output !is null) && (_bufLin.isInited()));
        }

        void process()
        {
            _time += 0.001;
            _lin.shader.getLocalByName("time").setData (_time);

            CFilter.process();

            setOutput(_output);
            _bufLin.bindAndAttachTexture2d(_output);

            _lin.textures[0] = _input[inTexName];
            _lin.textures[1] = _input[inTexName1];
            _lin.textures[2] = _input[inTexName2];
            _binder.bindMaterial (_lin);


            glDisable(GL_BLEND);
            _render.fullScreenQuad();

            _binder.unBindMaterial (_lin);
        }

        this(char[] idname, IRenderer bin)
        {
            super(idname, bin);
            _input[inTexName1] = null;
            _input[inTexName2] = null;

            _lin.bools["specialShaderUse"] = true;

            _lin.shader.setProg( sh_Vertex , CResShader( "data/v_distort.glsl", true ));
			_lin.shader.setProg( sh_Fragment, CResShader( "data/f_distort.glsl", true ));
			_lin.shader.addLocal( "image", 0 , true );
            _lin.shader.addLocal( "distsampler", 1 , true );
            _lin.shader.addLocal( "noise", 2 , true );
            _lin.shader.addLocal( "time", 3 , false );

            _lin.textures.length = 3;
        }
}

class CDOFFilter : CLinearFilter
{
    public:
        static const char[] inTexName1 = "intex1";
        static const char[] inTexName2 = "indepthtex2";

        bool isInit()
        {
            return (_input[inTexName2] !is null) && (_input[inTexName1] !is null) && (_input[inTexName] !is null) && (_output !is null) && (_bufLin.isInited());
        }

        void process()
        {
            CFilter.process();

            setOutput(_output);
            _bufLin.bindAndAttachTexture2d(_output);

            _lin.textures[0] = _input[inTexName];
            _lin.textures[1] = _input[inTexName1];
            _lin.textures[2] = _input[inTexName2];
            _binder.bindMaterial (_lin);


            glDisable(GL_BLEND);
            _render.fullScreenQuad();

            _binder.unBindMaterial (_lin);
        }

        this(char[] idname, IRenderer bin)
        {
            super(idname, bin);
            _input[inTexName1] = null;
            _input[inTexName2] = null;

            _lin.bools["specialShaderUse"] = true;

            _lin.shader.setProg( sh_Vertex , CResShader( "data/v_dof.glsl", true ));
			_lin.shader.setProg( sh_Fragment, CResShader( "data/f_dof.glsl", true ));
			_lin.shader.addLocal( "fullsampler", 0 , true );
			_lin.shader.addLocal( "blur", 1 , true );
			_lin.shader.addLocal( "depthsampler", 2 , true );
			_lin.shader.addLocal( "range", 0.018 , true );
			_lin.shader.addLocal( "focus", 50.0 , true );

            _lin.textures.length = 3;
        }
}


class CBlurFilter : CFilter
{
    protected:
        COpenGLFBO _bufBlur;
        CResMaterial _blur;
        Vector3f _blur_offset;

    public:
        static const char[] inTexName = "intex";

        void setBlurOffset(Vector3f vec)
        {
            _blur_offset = vec;
            _blur.shader.getLocalByName("blur_offset").setData (_blur_offset);
        }

        void process()
        {
            super.process();

            _binder.bindMaterial (_blur);

            _bufBlur.bindAndAttachTexture2d(_output);

            _binder.bindTexture(_input[inTexName]);
            glDisable(GL_BLEND);
            _render.fullScreenQuad();

            _binder.unBindMaterial (_blur);
        }

        bool isInit()
        {
            return (_input[inTexName] !is null) && (_output !is null) && (_bufBlur.isInited());;
        }

        bool setOutput(CResTexture tex)
        {
            _bufBlur.resize( tex.data[0].width, tex.data[0].height );
            return super.setOutput( tex );
        }

        this(char[] idname, IRenderer bin)
        {
            super(idname, bin);

            _input[inTexName] = null;
            _blur = CResMaterial((idname~"_matpass"));
            debug debugLog.trace( "CBlurFilter created a material " ~ _blur.id );

            _blur.shader.setProg (sh_Vertex , CResShader ("data/v_blur.glsl", true));
			_blur.shader.setProg (sh_Fragment, CResShader ("data/f_blur.glsl", true));
			_blur.shader.addLocal ("blurimage", 0 , true);
			_blur.shader.addLocal ("blur_offset", 1 , false);
            _blur.bools["specialShaderUse"] = true;

            _bufBlur = new COpenGLFBO(64,64,false);
            _bufBlur.init();
        }

        ~this()
        {
        	delete _bufBlur;
        }
}

class CPingPongFilter : CFilter
{
    protected:
        COpenGLFBO _buf,_bufPong;
        CResTexture _pongTex;

        int _wid = 64,
            _hei = 64;
        int _pongWid = 64,
            _pongHei = 64;

    public:
        static const char[] inTexName = "intex";

        void setPongResolution(int wid, int hei)
        {
            _pongWid = wid;
            _pongHei = hei;

            _pongTex.data[0].Resize( _pongWid, _pongHei );
            _render.resLoader.upLoadTexture( _pongTex );
            _bufPong.resize( _pongWid, _pongHei );
        }

        void process()
        {
            super.process();
            glColor3f(1,1,1);
            _bufPong.bindAndAttachTexture2d(_pongTex);

            _binder.bindTexture(_input[inTexName]);
            _render.fullScreenQuad();

            _buf.bindAndAttachTexture2d(_output);

            _binder.bindTexture(_pongTex);
            _render.fullScreenQuad();

            _buf.unBind;
        }

        bool isInit()
        {
            return (_input[inTexName] !is null) && (_output !is null) && (_buf.isInited() && (_bufPong.isInited()));;
        }

        bool setOutput(CResTexture tex)
        {
            _buf.resize( tex.data[0].width, tex.data[0].height );
            return super.setOutput( tex );
        }

        this(char[] idname, IRenderer bin)
        {
            super(idname, bin);

            _input[inTexName] = null;

            _buf = new COpenGLFBO(64,64,false);
            _buf.init();

            _bufPong = new COpenGLFBO(64,64,false);
            _bufPong.init();

            _pongTex = CResTexture(id~"_texPong");

            _pongTex.data~=CResTextureData(id~"_texPong_data", false);
            _pongTex.data[$-1].CreateTempTex(64, 64, IL_RGB, Tex_2d );
            _render.resLoader.upLoadTexture(_pongTex);
           // _bufPong.bindAndAttachTexture2d(_pongTex);

        }

        ~this()
        {
            delete _buf;
            delete _bufPong;
        }
}


/*wip
class CLuminanceFilter : CFilter
{
    protected:
        COpenGLFBO _buf64,_buf16,_buf4,_bufres;
        CResTexture _tex64,_tex16,_tex4;

    public:
        static const char[] inTexName = "intex";

        void process()
        {
            super.process();
            _buf64.bindAndAttachTexture2d(_tex64);
            _binder.bindTexture(_input[inTexName]);
            _render.fullScreenQuad();
            _buf16.bindAndAttachTexture2d(_tex16);
            _binder.bindTexture(_tex64);
            _render.fullScreenQuad();
            _buf4.bindAndAttachTexture2d(_tex4);
            _binder.bindTexture(_tex16);
            _render.fullScreenQuad();
            _bufres.bindAndAttachTexture2d(_output);
            _binder.bindTexture(_tex4);
            _render.fullScreenQuad();
            _bufres.unBind;
        }

        bool isInit()
        {
            return ((_input[inTexName] !is null) && (_output !is null) && (_bufres.isInited()) && (_buf64.isInited())&& (_buf16.isInited())
                    && (_buf4.isInited()) );
        }

        bool setOutput(CResTexture tex)
        {
            if( !((tex.data[0].height == 1) && (tex.data[0].width == 1)) )
                moonAssert(false, "output must be a texture with 1x1 size", __FILE__, __LINE__);

            return super.setOutput( tex );
        }

        this(char[] idname, IRenderer bin)
        {
            super(idname, bin);

            _input[inTexName] = null;

            _buf64 = new COpenGLFBO(64,64,false);
            _buf64.init();

            _buf16 = new COpenGLFBO(16,16,false);
            _buf16.init();

            _buf4 = new COpenGLFBO(4,4,false);
            _buf4.init();

            _bufres = new COpenGLFBO(1,1,false);
            _bufres.init();
///
            _tex64 = CResTexture(id~"_tex64");
            _tex64.data~=CResTextureData(id~"_tex64_data", false);
            _tex64.data[$-1].CreateTempTex(64, 64, IL_RGB, Tex_2d );
            _render.resLoader.upLoadTexture(_tex64);
            _buf64.bindAndAttachTexture2d(_tex64);
            _buf64.unBind();

            _tex16 = CResTexture(id~"_tex16");
            _tex16.data~=CResTextureData(id~"_tex16_data", false);
            _tex16.data[$-1].CreateTempTex(16, 16, IL_RGB, Tex_2d );
            _render.resLoader.upLoadTexture(_tex16);
            _buf16.bindAndAttachTexture2d(_tex16);
            _buf16.unBind();

            _tex4 = CResTexture(id~"_tex4");
            _tex4.data~=CResTextureData(id~"_tex4_data", false);
            _tex4.data[$-1].CreateTempTex(4, 4, IL_RGB, Tex_2d );
            _render.resLoader.upLoadTexture(_tex4);
            _buf4.bindAndAttachTexture2d(_tex4);
            _buf4.unBind();


        }

        ~this()
        {
            delete _bufres;
            delete _buf64;
            delete _buf16;
            delete _buf4;
        }
}
*/
